/***************************************************************************
Name:		client.cpp
Autor:		Kevin Huber
Version:	0.4 Alpha
Last Edit:      12.06.2009

Beschreibung:
Diese Klasse stellt einen Clienten dar mit dem interagiert werden kann.
***************************************************************************/

#include "client.h"
#include "clientmanager.h"
#include <string>
#include "chathandler.h"
#include "sqlconnection.h"

Client::Client(ClientManager *clientManager, unsigned int cId, QTcpSocket *socket, Client_Status cStatus, QObject *parent) : QObject(parent)
{
    m_id            = cId;
    m_socket        = socket;
    dataSize        = 0;
    m_isReadingData = false;

    isNotPlaying();
    m_nextClient = m_lastClient = NULL;

    m_name   = "Unknown";
    m_status = cStatus;

    m_clientManager = clientManager;

    m_chatHandler = new ChatHandler(this);

    // Events verknuepfen
    connect( m_socket, SIGNAL(disconnected()), this, SLOT(on_disconnected()) );
    connect( m_socket, SIGNAL(readyRead()), this, SLOT(readClientSocket()) );
    connect( m_chatHandler, SIGNAL(authCommand(QString)), this, SLOT(on_authCommand(QString)) );
}

Client::~Client()
{
    if( getLastClient() )
        getLastClient()->setNextClient(getNextClient());

    if( getNextClient() )
        getNextClient()->setLastClient(getLastClient());

    if( m_socket->isOpen() )
        m_socket->close();

    if( m_socket )
        m_socket->deleteLater();

    delete m_chatHandler;
}

void Client::setName(const QString &newName)
{
    m_name = newName;
}

void Client::setPlayingGame(const QString &gName)
{
    m_playing  = true;
    m_gameName = gName;
}

void Client::on_disconnected()
{
    m_status = disconnected;
    emit signal_disconnected(this);

    m_socket->deleteLater();
}

void Client::readClientSocket()
{
    QDataStream in(m_socket);
    in.setVersion(QDataStream::Qt_4_0);

    m_isReadingData = true;

    while( m_status != deleted && m_socket->bytesAvailable() ) // <--- Crashfix here
    {
        if( dataSize == 0 )
        {
            if( m_socket->bytesAvailable() < (int)sizeof(quint16) )
                return;

            in >> dataSize;
        }

        if( m_socket->bytesAvailable() < dataSize )
            return;

        // Paket einlesen
        Package package;
        if( fillPackage(&package, &in) )
        {
            // Paket verarbeiten
            if( !m_chatHandler->handlePackage(&package) )
                printf("Unhandled Package from typ: %d\n", package.typ);
        }
        else
            printf("Got an either unhandled or not acceptable package from Client %d!\n", m_id);

        dataSize = 0;
    }

    m_isReadingData = false;

    if( m_status == deleted )
        deleteLater(m_deleteReason);
}

bool Client::fillPackage(Package *package, QDataStream *incommingStream)
{
    // Get packagetyp
    *incommingStream >> package->typ;

    if( package->typ == TUNNEL_PACKAGETYP_MESSAGE )
    {
        package->from = m_name;
        *incommingStream >> package->message;

        // To send messages the client has to be connected and authentificated.
        if( m_status != connected )
            return false;

        return true;
    }
    else if( package->typ == TUNNEL_PACKAGETYP_SYSTEM )
    {
        *incommingStream >> package->commandCategory;
        *incommingStream >> package->command;
        *incommingStream >> package->arguments;

        return true;
    }
    else
        printf("Error: Unhandled packagetyp!: %d\n", package->typ);

    return false;
}

void Client::sendPackage(Package *package)
{
    if( !package )
        return;

    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_0);

    out << (quint16)0;
    out << package->typ;

    if( package->typ == TUNNEL_PACKAGETYP_SYSTEM )
    {
        out << package->commandCategory;
        out << package->command;
        out << package->arguments;
    }
    else if( package->typ == TUNNEL_PACKAGETYP_MESSAGE )
    {
        // To receive messages the client has to be connected and authentificated.
        if( m_status != connected )
            return;

        out << package->message;
        out << package->from;
    }
    else
    {
        printf("Tryed to send invalid package from typ: %d\n", package->typ);

        return;
    }

    // Send package
    out.device()->seek(0);
    out << (quint16)(block.size() - sizeof(quint16));

    m_socket->write(block);
}

void Client::on_authCommand(const QString &arguments)
{
    if( arguments.length() > 9 && arguments.toStdString().substr(0,9) == "AcceptMe:" )
    {
        // Arguments have the following syntax:'AcceptMe:loginName|password'
        // Check if loginName and password are matching to an account in our database
        QString passwordHash = arguments.mid( arguments.indexOf("|") + 1 );
        QString username     = arguments.mid( 9, arguments.toStdString().find("|") - 9 );

        if( m_clientManager->checkClientsLoginData(username, passwordHash))
        {
            quint32 uid = m_clientManager->getClientsUid(username);

            if( m_clientManager->getClient(uid) )
            {
                Client *client = m_clientManager->getClient(uid);
                Package package;

                setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_ERROR, TUNNEL_COMMAND_ERRORMESSAGE, "This account is used by antoher computer!\nYou got disconnected form the server.");
                client->sendPackage(&package);

                // For save delete we have to do this :S
                client->deleteLater("This account is used by antoher computer!");
            }

            // Changing the clients state to connected, so that he can gain full access to all functions
            Package package;

            setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_COMM, TUNNEL_COMMAND_AUTH, "Welcome");
            sendPackage(&package);

            // Update ClientData
            m_status = connected;
            m_id     = uid;
            setName(username);
            printf("Changed %s's id to %d\n", username.toStdString().c_str(), m_id);

            // Connect Clients events
            connect( m_chatHandler, SIGNAL(newMessage(QString)), this, SLOT(emitNewMessage(QString)) );
            connect( m_chatHandler, SIGNAL(getFriendlist()), this, SLOT(on_getFriendlist()));
            connect( m_chatHandler, SIGNAL(addFriend(QString)), this, SLOT(on_addFriend(QString)));
            connect( m_chatHandler, SIGNAL(getFriendrequests()), this, SLOT(on_getFriendrequests()) );
            connect( m_chatHandler, SIGNAL(acceptFriendInventation(const quint32&)), this, SLOT(on_acceptFriendInventation(const quint32&)));

            return;
        }
        else
        {
            // Counting violations is an good idea here?
            Package package;

            setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_COMM, TUNNEL_COMMAND_AUTH, "Refused:Wrong loginname and/or password!");
            sendPackage(&package);

            // For save delete we have to do this :S
            deleteLater("Wrong loginname and/or password!");
            return;
        }
    }
    else
        printf("Wrong Arguments for Auth: '%s'\n", arguments.toStdString().c_str());
}

void Client::on_getFriendlist()
{
    QString sFriendlist = ""; // String in wich we store the friendlist.

    if( m_status != connected )
        return;

    Package package;

    // Get Cliens friendlist
    std::vector<ClientFriend> myFriends(0);
    m_clientManager->getClientsFriendlist(&myFriends, m_id);

    // Convert all ClientFriend objects into an string and append it to the arguments part of the package...
    foreach( ClientFriend myFriend, myFriends )
    {
        // name:nickName:online;name2:nickname2:offline;...
        sFriendlist += myFriend.loginName + ":";
        sFriendlist += (myFriend.nickName.length() > 0 ? myFriend.nickName : myFriend.loginName) + ":"; // If the client has no nickname take the loginname.
        sFriendlist += (myFriend.isOnline ? "online" : "offline");
        sFriendlist += ";";

        if( myFriend.isOnline )
        {
            Client *myFriend_client = m_clientManager->getClient(myFriend.uid);

            // Connecting events between those two clients. <--- Do this on a later state of developing!
        }
    }

    setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_FRIENDS, TUNNEL_COMMAND_UPDATEFRIENDLIST, sFriendlist);
    sendPackage(&package);
}

void Client::on_addFriend(const QString &arguments)
{
    if( m_status != connected )
        return;
    if( arguments.length() < 1 )
        return;

    QString friend_loginName = arguments.left( arguments.indexOf(':') );
    QString rMessage         = arguments.mid(arguments.toStdString().find(':') + 1);
    quint32 friend_uid       = m_clientManager->getClientsUid(friend_loginName);

    if( friend_uid < 1 )
        return;

    // Should make some status messages for the client here!
    if( friend_loginName > 0 )
        m_clientManager->createFriendshipInventation(m_id, friend_loginName, rMessage);

    // If the client is currently loggedin, we send him his friendrequests.
    Client *myFriend = m_clientManager->getClient(friend_uid);
    if( myFriend )
    {
        myFriend->on_getFriendrequests();
    }
}

void Client::emitNewMessage(const QString &message)
{
    if( m_status == connected )
        emit signal_newMessage(m_name, message);
}

void Client::on_getFriendrequests()
{
    QString sFriendrequests = ""; // String in wich we store the friendrequests.

    if( m_status != connected )
        return;

    Package package;

    // Get friendrequests
    std::vector<ClientFriendrequest> friendrequests(0);
    m_clientManager->getClientFriendrequests(&friendrequests, m_id);

    // Convert all ClientFriendrequest objects into an string and append it to the arguments part of the package...
    foreach( ClientFriendrequest friendrequest, friendrequests )
    {
        // uid:nickName:requestMessage;uid2:nickname2:requestMessage;...
        sFriendrequests = sFriendrequests.sprintf("%d:", friendrequest.applicant_uid);
        sFriendrequests += (friendrequest.applicant_nickName.length() > 0 ? friendrequest.applicant_nickName : friendrequest.applicant_loginName) + ":"; // If the client has no nickname take the loginname.
        sFriendrequests += friendrequest.rMessage;
        sFriendrequests += ";";
    }

    setupSystemPackage(&package, TUNNEL_COMMANDCATEGORY_FRIENDS, TUNNEL_COMMAND_GETFRIENDREQUESTS, sFriendrequests);
    sendPackage(&package);
}

void Client::on_acceptFriendInventation(const quint32 &applicant_uid)
{
    if( m_status != connected )
        return;

    m_clientManager->acceptFriendshipInventation(m_id, applicant_uid);

    // If the client is currently loggedin, we send him his friendlist again.
    Client *myFriend = m_clientManager->getClient(applicant_uid);
    if( myFriend )
    {
        myFriend->on_getFriendlist();
    }
}

void Client::deleteLater(const QString &deleteReason)
{
    if( !m_isReadingData )
    {
        m_clientManager->removeClient(this, deleteReason);
    }
    else
    {
        m_status       = deleted;
        m_deleteReason = deleteReason;
    }
}
